package jp.co.nes.awssupport.access.dao;

import java.math.BigDecimal;
import java.time.YearMonth;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import javax.xml.bind.JAXBElement;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.namespace.QName;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.sforce.soap.enterprise.DeleteResult;
import com.sforce.soap.enterprise.Error;
import com.sforce.soap.enterprise.QueryResult;
import com.sforce.soap.enterprise.UpsertResult;
import com.sforce.soap.enterprise.sobject.AggregateResult;
import com.sforce.soap.enterprise.sobject.CampaignGroupC;
import com.sforce.soap.enterprise.sobject.CampaignGroupRelationInformationC;
import com.sforce.soap.enterprise.sobject.CampaignPlanC;
import com.sforce.soap.enterprise.sobject.Contact;
import com.sforce.soap.enterprise.sobject.ContractInformationC;
import com.sforce.soap.enterprise.sobject.CreditCouponC;
import com.sforce.soap.enterprise.sobject.CreditCouponHistoryC;
import com.sforce.soap.enterprise.sobject.ExchangeRateC;
import com.sforce.soap.enterprise.sobject.MiscellaneousExpensesGroupC;
import com.sforce.soap.enterprise.sobject.MiscellaneousExpensesPlanC;
import com.sforce.soap.enterprise.sobject.OtherContactC;
import com.sforce.soap.enterprise.sobject.PricingGroupC;
import com.sforce.soap.enterprise.sobject.SObject;
import com.sforce.soap.enterprise.sobject.SubContractInformationC;
import com.sforce.soap.enterprise.sobject.TaxGroupC;
import com.sforce.soap.enterprise.sobject.TaxPlanC;
import com.sforce.soap.enterprise.sobject.UsageHistoryC;
import com.sforce.soap.enterprise.sobject.VolumingGroupC;

import jp.co.nes.awssupport.common.base.CommonException;
import jp.co.nes.awssupport.common.bean.CampaignGroupBean;
import jp.co.nes.awssupport.common.bean.CampaignPlanBean;
import jp.co.nes.awssupport.common.bean.ContactBean;
import jp.co.nes.awssupport.common.bean.ContractInformationBean;
import jp.co.nes.awssupport.common.bean.CreditCouponBean;
import jp.co.nes.awssupport.common.bean.CreditCouponHistoryBean;
import jp.co.nes.awssupport.common.bean.ExchangeRateBean;
import jp.co.nes.awssupport.common.bean.IamUserBean;
import jp.co.nes.awssupport.common.bean.MiscellaneousExpensesGroupBean;
import jp.co.nes.awssupport.common.bean.MiscellaneousExpensesPlanBean;
import jp.co.nes.awssupport.common.bean.OtherContactBean;
import jp.co.nes.awssupport.common.bean.SubContractInformationBean;
import jp.co.nes.awssupport.common.bean.TaxGroupBean;
import jp.co.nes.awssupport.common.bean.TaxPlanBean;
import jp.co.nes.awssupport.common.bean.UsageHistoryBean;
import jp.co.nes.awssupport.common.constant.CreditCouponHistoryKind;
import jp.co.nes.awssupport.common.utility.AppInfoUtil;
import jp.co.nes.awssupport.common.utility.AwsKmsHelper;
import jp.co.nes.awssupport.common.utility.DateUtil;
import jp.co.nes.awssupport.common.utility.PropertiesUtil;
import jp.co.nes.awssupport.sforce.jaxws.SforceClient;

/**
 * Force.comにアクセスし、データを取得 or 更新するDAOクラスです。
 */
public class SforceManageDao {

    /** Logger */
    private static Logger logger = LoggerFactory.getLogger(SforceManageDao.class);

    /** Force.comクライアント */
    private static SforceClient client;

    /** SOQLに設定する際の日付型のフォーマット */
    private static final String TARGET_DATE_FM = "uuuu-MM-dd";

    /** 契約情報の為替レート種別の値 */
    private static final String[] RATE_KIND_VALUE = { "変動レート", "固定レート" };

    /** 契約情報の一覧を取得するSOQL */
    private static String soqlSelectContractInfo = ""
            + "select Id,"
            + "       LinkedAccountId__c,"
            + "       StartDate__c,"
            + "       SupportPlan__c,"
            + "       BillingPersonDepartmentShortName__c,"
            + "       BillingPersonCity__c,"
            + "       BillingPersonStreet__c,"
            + "       BillingPersonState__c,"
            + "       BillingPersonPostalCode__c,"
            + "       BillingPersonEmail__c,"
            + "       BillingPersonCompany__c,"
            + "       BillingPersonDepartment__c,"
            + "       BillingPersonTitle__c,"
            + "       BillingPerson__r.Name,"
            + "       VolumingGroup__r.GroupId__c,"
            + "       PricingGroup__r.GroupId__c,"
            + "       (select CampaignGroup__c"
            + "          from CGRIContractInformation__r),"
            + "       MiscellaneousExpensesGroup__r.Id,"
            + "       MiscellaneousExpensesGroup__r.GroupId__c,"
            + "       MiscellaneousExpensesGroup__r.MiscellaneousExpensesName__c,"
            + "       MiscellaneousExpensesGroup__r.Description__c,"
            + "       (select Id,"
            + "               CreditCouponId__c,"
            + "               ExpirationDate__c,"
            + "               CreditCouponBalance__c"
            + "          from CreditCouponContractInformation__r"
            + "         where ExpirationDate__c >= :ExpirationDate"
            + "         order by ExpirationDate__c, CreditCouponId__c),"
            + "       TaxGroup__r.Id,"
            + "       TaxGroup__r.GroupId__c,"
            + "       TaxGroup__r.TaxName__c,"
            + "       TaxGroup__r.Description__c,"
            + "       RateKind__c,"
            + "       ApiIamUserId__c,"
            + "       ApiIamAccessKey__c,"
            + "       ApiIamSecurityKey__c"
            + "  from ContractInformation__c"
            + " where LinkedAccountId__c != ''"
            + "   and NotNeedCompile__c = False"
            + "   and StartDate__c <  :StartDate"
            + "   and ( CancelDate__c = null or CancelDate__c >= :CancelDate )";

    /** 契約情報を取得するSOQL(新規アカウント処理) */
    private static String soqlSelectContractInfo_forNewAwsAccount = ""
            + " select Id,"
            + "         Name,"
            + "         LinkedAccountId__c,"
            + "         ApiIamAccessKey__c,"
            + "         ApiIamSecurityKey__c,"
            + "         IAMDefVersion__c,"
            + "         SubResale2__c,"
            + "         Usage__c,"
            + "         AccountNamePrefix__c,"
            + "         SupportPlan__c,"
            + "         AWSCloudTrail__c,"
            + "         RootMFA__c,"
            + "         TrustedAdvisorSecurityCheck__c,"
            + "         VpcFlowLogs__c,"
            + "         AwsConfig__c,"
            + "         ContractPersonCompany__c,"
            + "         ContractPerson__c,"
            + "         ContractPerson__r.ID,"
            + "         ContractPerson__r.Name,"
            + "         ContractPerson__r.FirstName,"
            + "         ContractPerson__r.LastName,"
            + "         ContractPerson__r.Department,"
            + "         ContractPerson__r.Email,"
            + "         ContractPerson__r.AccountID,"
            + "         BillingPersonCompany__c,"
            + "         BillingPerson__c,"
            + "         BillingPerson__r.ID,"
            + "         BillingPerson__r.Name,"
            + "         BillingPerson__r.FirstName,"
            + "         BillingPerson__r.LastName,"
            + "         BillingPerson__r.Department,"
            + "         BillingPerson__r.Email,"
            + "         BillingPerson__r.AccountID,"
            + "         ContactPersonCompany__c,"
            + "         ContactPerson__c,"
            + "         ContactPerson__r.ID,"
            + "         ContactPerson__r.Name,"
            + "         ContactPerson__r.FirstName,"
            + "         ContactPerson__r.LastName,"
            + "         ContactPerson__r.Department,"
            + "         ContactPerson__r.Email,"
            + "         ContactPerson__r.AccountID,"
            + "          (select LinkedAccountId__c,"
            + "                  Kind__c,"
            + "                  Company__c,"
            + "                  Person__r.ID,"
            + "                  Person__r.Name,"
            + "                  Person__r.FirstName,"
            + "                  Person__r.LastName,"
            + "                  Person__r.Department,"
            + "                  Person__r.Email,"
            + "                  Person__r.AccountID"
            + "             from OtherContactContractInformation__r"
            + "             where Kind__c = '請求明細送付先'"
            + "             and   SubContractInformation__c = ''"
            + "             )"
            + " from   ContractInformation__c"
            + " where  LinkedAccountId__c = :LinkedAccountId ";

    /** 契約情報の一覧を取得するSOQL(ご利用明細宛先リスト取得処理) */
    private static String soqlSelectContractInfo_forUsageReportAddressList = ""
            + " select Id,"
            + "          Name,"
            + "          LinkedAccountId__c,"
            + "          StartDate__c,"
            + "          CancelDate__c,"
            + "          SubResale2__c,"
            + "          AccountNamePrefix__c,"
            + "          SupportPlan__c,"
            + "          NeedExcelBilling__c, "
            + "          PricingGroup__r.GroupId__c,"
            + "          PricingGroup__r.PricingName__c,"
            + "          ContractPersonCompany__c,"
            + "          BillingPersonCompany__c,"
            + "          BillingPerson__c,"
            + "          BillingPerson__r.Name,"
            + "          BillingPerson__r.Department,"
            + "          BillingPerson__r.Email,"
            + "           (select Person__r.Email"
            + "              from OtherContactContractInformation__r"
            + "              where Kind__c in ( '請求明細送付先' , '営業' ) "
            + "              and   SubContractInformation__c = '' "
            + "              )"
            + "     from ContractInformation__c"
            + "     where NotNeedCompile__c = False"
            + "       and ContractPerson__r.InnerRelatives__c = False";

    /** 契約情報の一覧を取得するSOQL(レポートURL更新処理) */
    private static String soqlSelectContractInfo_forAmountReport = ""
            + "select Id,"
            + "       LinkedAccountId__c,"
            + "       PricingGroup__r.GroupId__c,"
            + "       RateKind__c"
            + "  from ContractInformation__c"
            + " where LinkedAccountId__c != ''"
            + "   and NotNeedCompile__c = False"
            + "   and StartDate__c <  :StartDate"
            + "   and ( CancelDate__c = null or CancelDate__c >= :CancelDate )";

    /** デフォルトのキャンペーングループを取得するSOQL */
    private static String soqlSelectCampaignGroupDefault = ""
            + "select GroupId__c,"
            + "       CampaignName__c,"
            + "       Description__c,"
            + "       (select PlanId__c,"
            + "               UsageType__c,"
            + "               ProductName__c,"
            + "               Coefficient__c,"
            + "               Constant__c,"
            + "               Description__c,"
            + "               StartDate__c,"
            + "               EndDate__c"
            + "          from CampaignPlanCampaignGroup__r"
            + "         where StartDate__c <= :StartDate"
            + "           and EndDate__c > :EndDate)"
            + "  from CampaignGroup__c"
            + " where GroupId__c = 'CPG0DEF000'";

    /** キャンペーングループを取得するSOQL */
    private static String soqlSelectCampaignGroup = ""
            + "select GroupId__c,"
            + "       CampaignName__c,"
            + "       Description__c,"
            + "       (select PlanId__c,"
            + "               UsageType__c,"
            + "               ProductName__c,"
            + "               Coefficient__c,"
            + "               Constant__c,"
            + "               Description__c,"
            + "               StartDate__c,"
            + "               EndDate__c"
            + "          from CampaignPlanCampaignGroup__r"
            + "         where StartDate__c <= :StartDate"
            + "           and EndDate__c > :EndDate)"
            + "  from CampaignGroup__c"
            + " where Id = :Id";

    /** 雑費プランの一覧を取得するSOQL */
    private static String soqlSelectMiscellaneousExpensesPlan = ""
            + "select Name,"
            + "       PlanId__c,"
            + "       Coefficient__c,"
            + "       Constant__c,"
            + "       Description__c,"
            + "       StartDate__c,"
            + "       EndDate__c"
            + "  from MiscellaneousExpensesPlan__c"
            + " where MiscellaneousExpensesGroup__c = :MiscellaneousExpensesGroup"
            + "   and StartDate__c <= :StartDate"
            + "   and EndDate__c > :EndDate";

    /** 税金プランの一覧を取得するSOQL */
    private static String soqlSelectTaxPlan = ""
            + "select PlanId__c,"
            + "       TaxRate__c,"
            + "       Description__c,"
            + "       StartDate__c,"
            + "       EndDate__c"
            + "  from TaxPlan__c"
            + " where TaxGroup__c = :TaxGroup"
            + "   and StartDate__c <= :StartDate"
            + "   and EndDate__c > :EndDate";

    /** 為替レートの一覧を取得するSOQL */
    private static String soqlSelectExchangeRate = ""
            + "select DollerYen__c,"
            + "       FixedRate__c,"
            + "       StartYearMonth__c,"
            + "       EndYearMonth__c"
            + "  from ExchangeRate__c"
            + " where StartYearMonth__c <= :StartYearMonth"
            + "   and EndYearMonth__c >= :EndYearMonth";

    /** クレジット履歴の一覧を取得するSOQL */
    private static String soqlSelectCreditCouponHistory = ""
            + "select sum(CreditCouponHistoryValue__c) CreditCouponHistoryValue"
            + "  from CreditCouponHistory__c"
            + " where CreditCoupon__c = :CreditCoupon"
            + "   and (CreditCouponHistoryYearMonth__c < :HistoryYearMonth or"
            + "        (CreditCouponHistoryYearMonth__c = :HistoryYearMonth and"
            + "         CreditCouponHistoryKind__c = '" + CreditCouponHistoryKind.ADD.getSforceValue() + "'))";

    /** クレジット履歴の一覧を取得するSOQL(Prefix) */
    private static String soqlSelectCreditCouponHistoryPrefix = ""
            + "select Id from CreditCouponHistory__c where ";

    /** クレジット履歴の一覧を取得するSOQL(Suffix) */
    private static String soqlSelectCreditCouponHistorySuffix01 = ""
            + "CreditCoupon__r.CreditCouponId__c in (:CreditCouponId)";

    /** クレジット履歴の一覧を取得するSOQL(Suffix) */
    private static String soqlSelectCreditCouponHistorySuffix02 = ""
            + "CreditCouponHistoryYearMonth__c = :CreditCouponHistoryYearMonth and "
            + "CreditCouponHistoryKind__c in ('" + CreditCouponHistoryKind.USE.getSforceValue() + "','"
            + CreditCouponHistoryKind.INVALID.getSforceValue() + "')";

    /** サブ契約情報の一覧を取得するSOQL */
    private static String soqlSelectSubContractInformation_3SL = ""
            + " select Id, "
            + "         Name, "
            + "         LinkedAccountId__c, "
            + "         BillingPersonCity__c, "
            + "         BillingPersonStreet__c, "
            + "         BillingPersonState__c, "
            + "         BillingPersonPostalCode__c, "
            + "         BillingPersonEmail__c, "
            + "         BillingPersonCompany__c, "
            + "         BillingPersonDepartment__c, "
            + "         BillingPersonTitle__c, "
            + "         BillingPerson__r.Name, "
            + "         ContractInformation__r.ApiIamUserId__c, "
            + "         ContractInformation__r.ApiIamAccessKey__c, "
            + "         ContractInformation__r.ApiIamSecurityKey__c, "
            + "         ContractInformation__r.RateKind__c , "
            + "        (select LinkedAccountId__c, "
            + "                 Email__c "
            + "            from otherContactSubContractInformation__r "
            + "           where Kind__c = '請求明細送付先' "
            + "              and IsDeleted = False "
            + "             and SubContractInformation__c != ''  ) "
            + "       from SubContractInformation__c "
            + "      where ContractInformation__c != '' "
            + "        and ContractInformation__r.LinkedAccountId__c != '' "
            + "        and ContractInformation__r.SubResale2__c = 'NEC産業SL事' "
            + "        and ContractInformation__r.StartDate__c <  :StartDate      "
            + "        and ( ContractInformation__r.CancelDate__c = null  "
            + "            or ContractInformation__r.CancelDate__c >= :CancelDate  ) "
            + "        and ContractInformation__r.NotNeedCompile__c = False ";

    /** サブ契約情報の一覧を取得するSOQL(ご利用明細宛先リスト取得処理) */
    private static String soqlSelectSubContractInformation_forUsageReportAddressList = ""
            + "select Id, "
            + "          Name, "
            + "          LinkedAccountId__c, "
            + "          ContractInformation__r.Name, "
            + "          ContractInformation__r.StartDate__c, "
            + "          ContractInformation__r.CancelDate__c, "
            + "          ContractInformation__r.SubResale2__c, "
            + "          ContractInformation__r.AccountNamePrefix__c, "
            + "          ContractInformation__r.SupportPlan__c, "
            + "          ContractInformation__r.NeedExcelBilling__c, "
            + "          ContractInformation__r.PricingGroupName__c, "
            + "          ContractPersonCompany__c, "
            + "          ContractPerson__r.Name, "
            + "          BillingPersonCompany__c, "
            + "          BillingPersonDepartment__c, "
            + "          BillingPersonEmail__c, "
            + "          BillingPersonTitle__c, "
            + "          BillingPerson__c, "
            + "          BillingPerson__r.Name, "
            + "           (select Person__r.Email "
            + "              from otherContactSubContractInformation__r "
            + "              where Kind__c in ( '請求明細送付先' , '営業' )  "
            + "              and   SubContractInformation__c != '' "
            + "              ) "
            + " from SubContractInformation__c "
            + " where ContractInformation__r.NotNeedCompile__c = False "
            + " and ContractPerson__r.InnerRelatives__c = False    "
            + " and ContractInformation__r.SubResale2__c = 'NEC産業SL事'    ";

    /** 利用実績の対象行を取得するSOQL */
    private static String soqlSelectUsageHistory = ""
            + " select ID "
            + "       ,Name "
            + "       ,ExId__c "
            + "       ,LinkedAccountId__c "
            + "       ,UsageDetailReportPDF__c "
            + "       ,UsageDetailReportXLS__c "
            + "       ,AmountPerTagReportCSV__c "
            + "       ,AmountPerResourceIdReportCSV__c "
            + "   from UsageHistory__c "
            + "  where Name =  :targetYearMonth "
            + "    and LinkedAccountId__c = :LinkedAccountId ";

    /** SOQL内で文字列を括る文字。シングルクオート */
    private static String arroundSingleQuote = "'";

    /** SOQL内で値を列挙する際のセパレータ文字。カンマ */
    private static String separatorCamma = ",";

    /** 取得したキャンペーングループをキャッシュするマップ(Key=キャンペーングループのID) */
    private static Map<String, CampaignGroupBean> campaignGroupMap = new HashMap<>();

    /** 取得した雑費グループをキャッシュするマップ(Key=雑費グループのID) */
    private static Map<String, MiscellaneousExpensesGroupBean> miscellaneousExpensesGroupMap = new HashMap<>();

    /** 取得した税金グループをキャッシュするマップ(Key=税金グループのID) */
    private static Map<String, TaxGroupBean> taxGroupMap = new HashMap<>();

    /**
     * デフォルトコンストラクタです。<br>
     * 本クラスはインスタンスを生成しません。
     */
    private SforceManageDao() {
    }

    /**
     * 契約情報を返却します。<br>
     * なお、引数のtargetLinkedAccountIdListがnullの場合、全ての連結アカウントIDを処理対象として、返却します。
     *
     * @param targetLinkedAccountIdList 処理対象とする連結アカウントIDリスト
     * @param targetYearMonth 処理対象年月
     * @return 契約情報
     */
    public static List<ContractInformationBean> getContractInformationList(List<String> targetLinkedAccountIdList,
            YearMonth targetYearMonth) {

        // 実行するSOQLを編集
        String query = soqlSelectContractInfo;
        String targetDate = getTargetDate(targetYearMonth);
        query = query.replace(":ExpirationDate", targetDate);

        // 契約情報取得時、契約開始日、解約日の条件を追加
        String startDate = getTargetDate(targetYearMonth.plusMonths(1));
        query = query.replace(":StartDate", startDate);
        query = query.replace(":CancelDate", targetDate);

        List<ContractInformationBean> contractInfoList = loadContractInformation(query, targetYearMonth);
        if (targetLinkedAccountIdList != null) {
            return contractInfoList.stream().filter(c -> {
                String linkedAccountId = c.getLinkedAccountId();
                return targetLinkedAccountIdList.stream().anyMatch(s -> s.equals(linkedAccountId));
            }).collect(Collectors.toList());
        }
        return contractInfoList;
    }

    /**
     * 契約情報を返却します。(新規アカウント作成処理用)<br>
     * なお、引数のtargetLinkedAccountIdListがnullの場合、全ての連結アカウントIDを処理対象として、返却します。
     *
     * @param linkedAccountId 処理対象とする連結アカウントID
     * @param targetYearMonth 処理対象年月
     * @return 契約情報
     */
    public static ContractInformationBean getContractInfo_NewAwsAccount(String linkedAccountId) {

        // 実行するSOQLを編集
        String query = soqlSelectContractInfo_forNewAwsAccount;
        query = query.replace(":LinkedAccountId", appendSingleQuote(linkedAccountId));

        List<ContractInformationBean> contractInfoList = loadContractInformation(query, null);
        if (contractInfoList != null && contractInfoList.size() > 0) {
            return contractInfoList.get(0);
        }
        return null;
    }

    /**
     * 契約情報を返却します。(レポートURL更新処理用)<br>
     * なお、引数のtargetLinkedAccountIdListがnullの場合、全ての連結アカウントIDを処理対象として、返却します。
     *
     * @param targetLinkedAccountIdList 処理対象とする連結アカウントIDリスト
     * @param targetYearMonth 処理対象年月
     * @return 契約情報
     */
    public static List<ContractInformationBean> getContractInformationList_AmountReport(
            List<String> targetLinkedAccountIdList, YearMonth targetYearMonth) {

        // 実行するSOQLを編集
        String query = soqlSelectContractInfo_forAmountReport;

        // 契約情報取得時、契約開始日、解約日の条件を追加
        String targetDate = getTargetDate(targetYearMonth);
        String startDate = getTargetDate(targetYearMonth.plusMonths(1));
        query = query.replace(":StartDate", startDate);
        query = query.replace(":CancelDate", targetDate);

        List<ContractInformationBean> contractInfoList = loadContractInformation(query, targetYearMonth);
        if (targetLinkedAccountIdList != null && targetLinkedAccountIdList.size() != 0) {
            return contractInfoList.stream().filter(c -> {
                String linkedAccountId = c.getLinkedAccountId();
                return targetLinkedAccountIdList.stream().anyMatch(s -> s.equals(linkedAccountId));
            }).collect(Collectors.toList());
        }
        return contractInfoList;
    }

    /**
     * 契約情報を返却します。(ご利用明細宛先リスト取得処理用)<br>
     *
     * @param なし
     * @return 契約情報
     */
    public static List<ContractInformationBean> getContractInfoList_forUsageReportAddressList() {

        // 実行するSOQLを編集
        String query = soqlSelectContractInfo_forUsageReportAddressList;

        return loadContractInformation(query, null);
    }

    /**
     * 設定された全ての情報(クレジット履歴と利用実績)をForce.comへ保存します。
     *
     * @param isAllTargetLinkedAccountId true: 全ての連結アカウントが処理対象である, false: 全ての連結アカウントが処理対象でない
     * @param contractInfoList 契約情報リスト
     * @param targetYearMonth 処理対象年月
     */
    public static void saveAll(boolean isAllTargetLinkedAccountId, List<ContractInformationBean> contractInfoList,
            YearMonth targetYearMonth) {
        login();
        try {
            SaveHelper saveHelper = new SaveHelper(contractInfoList);
            List<String> creditCouponIdList = saveHelper.getCreditCouponIdList();
            List<SObject> sCCHObjList = saveHelper.getSCCHObjList();
            List<SObject> sUHObjList = saveHelper.getSUHObjList();

            if (!creditCouponIdList.isEmpty()) {
                // 削除するクレジット履歴のIDを取得する。
                String query = soqlSelectCreditCouponHistoryPrefix;
                if (!isAllTargetLinkedAccountId) {
                    query = query + soqlSelectCreditCouponHistorySuffix01 + " and ";
                }
                query = query + soqlSelectCreditCouponHistorySuffix02;
                if (!isAllTargetLinkedAccountId) {
                    String joinCreditCouponId = join(creditCouponIdList);
                    query = query.replace(":CreditCouponId", joinCreditCouponId);
                }
                query = query.replace(":CreditCouponHistoryYearMonth",
                        appendSingleQuote(DateUtil.toString(targetYearMonth)));

                if (logger.isDebugEnabled()) {
                    logger.debug("soqlSelectCreditCouponHistory=" + query);
                }

                QueryResult sDelResult = client.query(query);
                List<SObject> delList = sDelResult.getRecords();
                List<String> delIdList = delList.stream().map(sObj -> sObj.getId()).collect(Collectors.toList());

                // 削除するクレジット履歴のIDが存在した場合、クレジット履歴を削除する。
                delete(delIdList);
            }

            // クレジット履歴を登録する。
            upsert(sCCHObjList);

            // 利用実績を登録・修正する。
            upsert(sUHObjList);
        } finally {
            logout();
        }
    }

    /**
     * 利用実績をForce.comへ保存します。
     *
     * @param contractInfoList 契約情報リスト
     */
    public static void saveUsageHistory(List<ContractInformationBean> contractInfoList) {
        login();
        try {
            SaveHelper saveHelper = new SaveHelper(contractInfoList);
            List<SObject> sUHObjList = saveHelper.getSUHObjList();

            // 利用実績を登録・修正する。
            upsert(sUHObjList);
        } finally {
            logout();
        }
    }

    /**
     * サブ契約情報を返却します。<br>
     *
     * @param targetYearMonth 処理対象年月
     * @return サブ契約情報
     */
    public static List<SubContractInformationBean> getSubContractInformationList_3SL(YearMonth targetYearMonth) {

        String startDate = getTargetDate(targetYearMonth.plusMonths(1));
        String cancelDate = getTargetDate(targetYearMonth);
        String query = soqlSelectSubContractInformation_3SL;
        query = query.replace(":StartDate", startDate);
        query = query.replace(":CancelDate", cancelDate);

        List<SubContractInformationBean> subContractInfoList = loadSubContractInformation(query);
        return subContractInfoList;
    }

    /**
     * サブ契約情報を返却します。(ご利用明細宛先リスト取得処理)<br>
     *
     * @return サブ契約情報
     */
    public static List<SubContractInformationBean> getSubContractInformationList_forUsageReportAddressList() {

        String query = soqlSelectSubContractInformation_forUsageReportAddressList;

        List<SubContractInformationBean> subContractInfoList = loadSubContractInformation(query);
        return subContractInfoList;
    }

    /**
     * 為替レートマップを返却します。<br>
     *
     * @param targetYearMonth 処理対象年月
     * @return 為替レート
     */
    public static Map<String, ExchangeRateBean> getExchangeRateMap(YearMonth targetYearMonth) {
        Map<String, ExchangeRateBean> rateMap;
        login();
        rateMap = null;
        try {
            rateMap = loadExchangeRate(targetYearMonth);
        } finally {
            logout();
        }
        return rateMap;
    }

    /**
     * 契約情報を更新します。(新規アカウント作成時処理用)<br>
     *
     * @param bean 契約情報Bean
     * @return 更新結果
     */
    public static boolean saveContractInfo_NewAwsAccount(ContractInformationBean bean) {

        boolean rc = true;

        login();

        List<UpsertResult> resultList = new ArrayList<UpsertResult>();

        try {
            String query = soqlSelectContractInfo_forNewAwsAccount;
            ArrayList<SObject> beanList = new ArrayList<SObject>();

            IamUserBean iamBean = bean.getApiIamUser();

            if (iamBean != null) {
                query = query.replace(":LinkedAccountId", appendSingleQuote(bean.getLinkedAccountId()));
                QueryResult sContractInfoResult = client.query(query);

                for (SObject sContractInfoObj : sContractInfoResult.getRecords()) {
                    ContractInformationC sContractInfo = (ContractInformationC) sContractInfoObj;

                    ContractInformationC param = new ContractInformationC();

                    param.setId(sContractInfo.getId());
                    param.setLinkedAccountIdC(sContractInfo.getLinkedAccountIdC());
                    param.setApiIamAccessKeyC(getJAXBElement("ApiIamAccessKey__c", iamBean.getAccessKey()));
                    param.setApiIamSecurityKeyC(getJAXBElement("ApiIamSecurityKey__c", iamBean.getSecurityKey()));
                    param.setIAMDefVersionC(getJAXBElement("IAMDefVersion__c", iamBean.getDefVersion()));

                    beanList.add(param);

                    // 単一行が前提のためbreak。
                    break;
                }

                if (beanList.size() > 0) {
                    resultList = upsert(beanList, "LinkedAccountId__c");
                }
            }

        } finally {
            logout();
        }

        // 更新エラーの存在有無チェック
        for (UpsertResult result : resultList) {
            if (!result.isSuccess()) {
                rc = false;
                break;
            }
        }

        return rc;

    }

    /**
     * 利用実績を更新します。(レポートURL更新処理用)<br>
     *
     * @param bean 利用実績リスト
     * @return 更新結果
     */
    public static boolean saveUsageHistory_URLUpdate(List<UsageHistoryBean> usageHistoryList) {

        boolean rc = true;
        login();

        try {

            for (UsageHistoryBean bean : usageHistoryList) {
                List<UpsertResult> resultList = new ArrayList<UpsertResult>();

                String query = soqlSelectUsageHistory;
                ArrayList<SObject> beanList = new ArrayList<SObject>();

                query = query.replace(":LinkedAccountId", appendSingleQuote(bean.getLinkedAccountId()));
                query = query.replace(":targetYearMonth", appendSingleQuote(bean.getName()));

                QueryResult sUsageHistoryResult = client.query(query);

                for (SObject sUsageHistoryObj : sUsageHistoryResult.getRecords()) {
                    UsageHistoryC sUsageHistory = (UsageHistoryC) sUsageHistoryObj;

                    UsageHistoryC param = new UsageHistoryC();

                    param.setExIdC(sUsageHistory.getExIdC());

                    // 各レポートでURLが設定されている項目のみ更新する。
                    if (bean.getUsageDetailReportPDF() != null) {
                        param.setUsageDetailReportPDFC(
                                getJAXBElement("UsageDetailReportPDF__c", bean.getUsageDetailReportPDF()));
                    }

                    if (bean.getUsageDetailReportXLS() != null) {
                        param.setUsageDetailReportXLSC(
                                getJAXBElement("UsageDetailReportXLS__c", bean.getUsageDetailReportXLS()));
                    }

                    if (bean.getAmountPerTagReportCSV() != null) {
                        param.setAmountPerTagReportCSVC(getJAXBElement("AmountPerTagReportCSV__c",
                                bean.getAmountPerTagReportCSV()));
                    }

                    if (bean.getAmountPerResourceIdReportCSV() != null) {
                        param.setAmountPerResourceIdReportCSVC(getJAXBElement("AmountPerResourceIdReportCSV__c",
                                bean.getAmountPerResourceIdReportCSV()));
                    }

                    if (bean.getAmountPerTagResourceIdReportCSV() != null) {
                        param.setAmountPerTagResourceIdReportCSVC(getJAXBElement("AmountPerTagResourceIdReportCSV__c",
                                bean.getAmountPerTagResourceIdReportCSV()));
                    }

                    beanList.add(param);

                    // 単一行が前提のためbreak。
                    break;
                }

                if (beanList.size() > 0) {
                    resultList = upsert(beanList, "ExId__c");
                } else {

                    // エラー情報をログ出力
                    logger.error("アカウントID：" + bean.getLinkedAccountId() + " 年月：" + bean.getName()
                            + "レポートURLの更新に失敗しました。更新対象レコードがありません。");
                }

                // 更新エラーの存在有無チェック
                for (UpsertResult result : resultList) {
                    if (!result.isSuccess()) {
                        rc = false;

                        // エラー情報をログ出力
                        logger.error("アカウントID：" + bean.getLinkedAccountId() + " 年月：" + bean.getName()
                                + "レポートURLの更新に失敗しました。");
                        for (Error err : result.getErrors()) {
                            logger.error(err.getMessage());
                        }
                        break;
                    }
                }
                // エラーが発生しても、次アイテムへの更新処理を継続する
            }
        } finally {
            logout();
        }

        return rc;

    }

    /**
     * Saveヘルパークラスです。
     */
    static class SaveHelper {

        /** 削除するクレジット履歴のIDを取得する際に使用するリスト */
        private List<String> creditCouponIdList = new ArrayList<>();

        /** クレジット履歴を登録する際に使用するリスト */
        private List<SObject> sCCHObjList = new ArrayList<>();

        /** 利用実績を登録する際に使用するリスト */
        private List<SObject> sUHObjList = new ArrayList<>();

        /**
         * 契約情報を引数にもつコンストラクタです。
         *
         * @param contractInfoList 契約情報
         */
        public SaveHelper(List<ContractInformationBean> contractInfoList) {
            create(contractInfoList);
        }

        /**
         * 削除するクレジット履歴のIDを取得する際に使用するリストを返却します。
         *
         * @return 削除するクレジット履歴のIDを取得する際に使用するリスト
         */
        public List<String> getCreditCouponIdList() {
            return creditCouponIdList;
        }

        /**
         * クレジット履歴を登録する際に使用するリストを返却します。
         *
         * @return クレジット履歴を登録する際に使用するリスト
         */
        public List<SObject> getSCCHObjList() {
            return sCCHObjList;
        }

        /**
         * 利用実績を登録する際に使用するリストを返却します。
         *
         * @return 利用実績を登録する際に使用するリスト
         */
        public List<SObject> getSUHObjList() {
            return sUHObjList;
        }

        /**
         * 各種リストを作成します。
         *
         * @param contractInfoList 契約情報リスト
         */
        private void create(List<ContractInformationBean> contractInfoList) {
            for (ContractInformationBean cib : contractInfoList) {
                // クレジット履歴に関連する処理
                List<CreditCouponBean> ccList = cib.getCreditCouponList();
                if (ccList == null) {
                    // クレジットがない場合、通常は空リストが設定されて、nullでない。
                    // よって、通常、本処理が実行されることはない。
                    continue;
                }
                for (CreditCouponBean ccb : ccList) {
                    creditCouponIdList.add(ccb.getCreditCouponId());
                    List<CreditCouponHistoryBean> cchList = ccb.getCreditCouponHistoryList();
                    if (cchList == null) {
                        continue;
                    }
                    for (CreditCouponHistoryBean cchb : cchList) {
                        if (logger.isDebugEnabled()) {
                            logger.debug("CreditCouponBean=" + ccb.toString() + ",CreditCouponHistoryBean="
                                    + cchb.toString());
                        }

                        CreditCouponHistoryC sCCHObj = new CreditCouponHistoryC();
                        sCCHObj.setName(getJAXBElement("Name", cchb.getName()));
                        sCCHObj.setExIdC(getJAXBElement("ExId__c", cchb.getExId()));
                        sCCHObj.setCreditCouponHistoryKindC(getJAXBElement("CreditCouponHistoryKind__c",
                                cchb.getCreditCouponHistoryKind()));
                        sCCHObj.setCreditCouponHistoryYearMonthC(getJAXBElement("CreditCouponHistoryYearMonth__c",
                                cchb.getCreditCouponHistoryYearMonth()));
                        Double dValue = new Double(cchb.getCreditCouponHistoryValue().toString());
                        sCCHObj.setCreditCouponHistoryValueC(getJAXBElement("CreditCouponHistoryValue__c", dValue));
                        sCCHObj.setCreditCouponC(getJAXBElement("CreditCoupon__c", ccb.getId()));
                        sCCHObjList.add((SObject) sCCHObj);
                    }
                }

                // 利用実績に関する処理
                UsageHistoryBean usageHistoryBean = cib.getUsageHistoryBean();
                if (usageHistoryBean != null) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("UsageHistoryBean=" + usageHistoryBean.toString());
                    }

                    UsageHistoryC sUHObj = new UsageHistoryC();
                    sUHObj.setName(getJAXBElement("Name", usageHistoryBean.getName()));
                    sUHObj.setExIdC(getJAXBElement("ExId__c", usageHistoryBean.getExId()));
                    Double billingAmount = new Double(Long.toString(usageHistoryBean.getBillingAmount()));
                    sUHObj.setBillingAmountC(getJAXBElement("BillingAmount__c", billingAmount));
                    Double billingAmountWithoutTax = new Double(
                            Long.toString(usageHistoryBean.getBillingAmountWithoutTax()));
                    sUHObj.setBillingAmountWithoutTaxC(
                            getJAXBElement("BillingAmountWithoutTax__c", billingAmountWithoutTax));
                    sUHObj.setContractInformationC(getJAXBElement("ContractInformation__c", cib.getId()));
                    sUHObjList.add(sUHObj);
                } else {
                    // 利用実績が設定されていなかった場合、ワーニングログを出力する。
                    logger.warn("SaveHelper.crate：usageHistoryBean is null(LinkedAccountId=" + cib.getLinkedAccountId()
                            + ")");
                }
            }
        }
    }

    /**
     * Force.comから集計対象の契約情報を取得します。
     *
     * @param query 実行するSOQL
     * @param targetYearMonth 処理対象年月
     * @return 集計対象の契約情報のリスト
     */
    private static List<ContractInformationBean> loadContractInformation(String query, YearMonth targetYearMonth) {
        login();

        List<ContractInformationBean> contractInfoList = new ArrayList<>();
        try {
            QueryResult sContractInfoResult = client.query(query);

            List<CampaignGroupBean> campaignGroupDefaultList = null;
            Map<String, ExchangeRateBean> exchangeRateMap = null;

            if (targetYearMonth != null) {
                // デフォルトのキャンペーングループと為替レートを取得する。
                campaignGroupDefaultList = loadCampaignGroupDefaultList(targetYearMonth);
                exchangeRateMap = loadExchangeRate(targetYearMonth);
            }

            // 契約情報リストを作成する。
            List<SObject> sContractInfoList = sContractInfoResult.getRecords();
            for (SObject sContractInfoObj : sContractInfoList) {
                ContractInformationC sContractInfo = (ContractInformationC) sContractInfoObj;

                ContractInformationBean bean = new ContractInformationBean();
                bean.setId(sContractInfo.getId());
                bean.setLinkedAccountId(getValue(sContractInfo.getLinkedAccountIdC()));
                bean.setBillingPersonDepartmentShortName(
                        getValue(sContractInfo.getBillingPersonDepartmentShortNameC()));
                bean.setBillingPersonCity(getValue(sContractInfo.getBillingPersonCityC()));
                bean.setBillingPersonStreet(getValue(sContractInfo.getBillingPersonStreetC()));
                bean.setBillingPersonState(getValue(sContractInfo.getBillingPersonStateC()));
                bean.setBillingPersonPostalCode(getValue(sContractInfo.getBillingPersonPostalCodeC()));
                bean.setBillingPersonEmail(getValue(sContractInfo.getBillingPersonEmailC()));
                bean.setBillingPersonCompany(getValue(sContractInfo.getBillingPersonCompanyC()));
                bean.setBillingPersonDepartment(getValue(sContractInfo.getBillingPersonDepartmentC()));
                bean.setBillingPersonTitle(getValue(sContractInfo.getBillingPersonTitleC()));

                bean.setNeedExcelBilling(getBoolValue(sContractInfo.getNeedExcelBillingC(), false));
                bean.setSubResale2(getValue(sContractInfo.getSubResale2C()));
                bean.setContractNumber(getValue(sContractInfo.getName()));
                bean.setUsage(getValue(sContractInfo.getUsageC()));
                bean.setAccountNamePrefix(getValue(sContractInfo.getAccountNamePrefixC()));
                bean.setSupportPlan(getValue(sContractInfo.getSupportPlanC()));

                bean.setAwsCloudTrail(getBoolValue(sContractInfo.getAwsCloudTrailC(), false));
                bean.setRootMFA(getBoolValue(sContractInfo.getRootMFAC(), false));
                bean.setTrustedAdvisorSecurityCheck(
                        getBoolValue(sContractInfo.getTrustedAdvisorSecurityCheckC(), false));
                bean.setVpcFlowLogs(getBoolValue(sContractInfo.getVpcFlowLogsC(), false));
                bean.setAwsConfig(getBoolValue(sContractInfo.getAwsConfigC(), false));

                bean.setContactPersonCompany(getValue(sContractInfo.getContactPersonCompanyC()));

                bean.setContractPersonCompany(getValue(sContractInfo.getContractPersonCompanyC()));

                bean.setStartDate(getDateValue(sContractInfo.getStartDateC()));
                bean.setCancelDate(getDateValue(sContractInfo.getCancelDateC()));

                // 請求先担当者の設定
                Contact billing = getValue(sContractInfo.getBillingPersonR());
                if (billing != null) {
                    // 請求先担当者名の設定
                    bean.setBillingPersonName(getValue(billing.getName()));

                    ContactBean billingBean = new ContactBean();
                    billingBean.setId(billing.getId());
                    billingBean.setName(getValue(billing.getName()));
                    billingBean.setAccountId(getValue(billing.getAccountId()));
                    billingBean.setFirstName(getValue(billing.getFirstName()));
                    billingBean.setLastName(getValue(billing.getLastName()));
                    billingBean.setDepartment(getValue(billing.getDepartment()));
                    billingBean.setEmail(getValue(billing.getEmail()));
                    bean.setBillingPersonBean(billingBean);
                }

                // 契約責任者の設定
                Contact contract = getValue(sContractInfo.getContractPersonR());
                if (contract != null) {
                    ContactBean contractBean = new ContactBean();
                    contractBean.setId(contract.getId());
                    contractBean.setName(getValue(contract.getName()));
                    contractBean.setAccountId(getValue(contract.getAccountId()));
                    contractBean.setFirstName(getValue(contract.getFirstName()));
                    contractBean.setLastName(getValue(contract.getLastName()));
                    contractBean.setDepartment(getValue(contract.getDepartment()));
                    contractBean.setEmail(getValue(contract.getEmail()));
                    bean.setContractPersonBean(contractBean);
                }

                // 問合せ担当者の設定
                Contact contact = getValue(sContractInfo.getContactPersonR());
                if (contact != null) {
                    ContactBean contactBean = new ContactBean();
                    contactBean.setId(contact.getId());
                    contactBean.setName(getValue(contact.getName()));
                    contactBean.setAccountId(getValue(contact.getAccountId()));
                    contactBean.setFirstName(getValue(contact.getFirstName()));
                    contactBean.setLastName(getValue(contact.getLastName()));
                    contactBean.setDepartment(getValue(contact.getDepartment()));
                    contactBean.setEmail(getValue(contact.getEmail()));
                    bean.setContactPersonBean(contactBean);
                }

                // ボリューミンググループIDの設定
                VolumingGroupC volumingGroup = getValue(sContractInfo.getVolumingGroupR());
                if (volumingGroup == null) {
                    // volumingGroup を取得しないSOQLでエラーとなるため、コメントアウト
                    // if (targetYearMonth != null) {
                    //     throw new CompileException("volumingGroup is null");
                    // }
                } else {
                    bean.setVolumingGroupId(getValue(volumingGroup.getGroupIdC()));
                }

                // 料金グループIDの設定
                PricingGroupC pricingGroup = getValue(sContractInfo.getPricingGroupR());
                if (pricingGroup == null) {
                    // pricingGroup を取得しないSOQLでエラーとなるため、コメントアウト
                    // if (targetYearMonth != null) {
                    //     throw new CompileException("pricingGroup is null");
                    // }
                } else {
                    bean.setPricingGroupId(getValue(pricingGroup.getGroupIdC()));
                    bean.setPricingGroupName(getValue(pricingGroup.getPricingNameC()));
                }

                // キャンペーングループリストの設定
                QueryResult sCGRIResult = getValue(sContractInfo.getCGRIContractInformationR());

                if (targetYearMonth != null) {
                    List<CampaignGroupBean> campaignGroupList = loadCampaignGroupList(sCGRIResult, targetYearMonth);
                    campaignGroupList.addAll(campaignGroupDefaultList);
                    bean.setCampaignGroupList(campaignGroupList);

                    // 雑費グループの設定
                    bean.setMiscellaneousExpensesGroupBean(loadMiscellaneousExpensesGroup(
                            getValue(sContractInfo.getMiscellaneousExpensesGroupR()), targetYearMonth));

                    // クレジットの設定
                    QueryResult sCreditCouponResult = getValue(sContractInfo.getCreditCouponContractInformationR());
                    bean.setCreditCouponList(loadCreditCouponList(sCreditCouponResult, targetYearMonth));

                    // 税金グループの設定
                    bean.setTaxGroupBean(loadTaxGroup(getValue(sContractInfo.getTaxGroupR()), targetYearMonth));

                    // 為替レートの設定
                    String rateKind = getValue(sContractInfo.getRateKindC());
                    if (rateKind == null) {
                        throw new CommonException("rateKind is null");
                    }
                    bean.setExchangeRateBean(exchangeRateMap.get(rateKind));

                }

                // IAMユーザ関連の設定
                IamUserBean iamUserBean = new IamUserBean();
                iamUserBean.setIamUserId(getValue(sContractInfo.getApiIamUserIdC()));
                iamUserBean.setAccessKey(getValue(sContractInfo.getApiIamAccessKeyC()));
                iamUserBean.setSecurityKey(getValue(sContractInfo.getApiIamSecurityKeyC()));
                iamUserBean.setDefVersion(getValue(sContractInfo.getIAMDefVersionC()));

                bean.setApiIamUser(iamUserBean);

                // その他連絡先情報の設定
                QueryResult otherQR = getValue(sContractInfo.getOtherContactContractInformationR());
                if (otherQR == null) {
                    ArrayList<OtherContactBean> otherList = new ArrayList<OtherContactBean>();
                    bean.setOtherContact(otherList);

                } else {
                    OtherContactC sOther = null;
                    List<SObject> sOtherList = otherQR.getRecords();
                    ArrayList<OtherContactBean> otherList = new ArrayList<OtherContactBean>();
                    for (SObject sOtherObj : sOtherList) {
                        sOther = (OtherContactC) sOtherObj;
                        OtherContactBean otherBean = new OtherContactBean();
                        otherBean.setLinkedAccountId(getValue(sOther.getLinkedAccountIdC()));
                        otherBean.setKind(getValue(sOther.getKindC()));
                        otherBean.setCompany(getValue(sOther.getCompanyC()));

                        Contact person = getValue(sOther.getPersonR());
                        if (person != null) {
                            ContactBean personBean = new ContactBean();
                            personBean.setId(person.getId());
                            personBean.setName(getValue(person.getName()));
                            personBean.setAccountId(getValue(person.getAccountId()));
                            personBean.setFirstName(getValue(person.getFirstName()));
                            personBean.setLastName(getValue(person.getLastName()));
                            personBean.setDepartment(getValue(person.getDepartment()));
                            personBean.setEmail(getValue(person.getEmail()));
                            otherBean.setPersonBean(personBean);
                        }
                        otherList.add(otherBean);
                    }

                    bean.setOtherContact(otherList);
                }

                contractInfoList.add(bean);
            }
        } finally {
            logout();
        }
        return contractInfoList;
    }

    /**
     * 為替レートを取得します。
     *
     * @param targetYearMonth 処理対象年月
     * @return 為替レートマップ(固定レート、変動レートの2種類が格納されています)
     */
    private static Map<String, ExchangeRateBean> loadExchangeRate(YearMonth targetYearMonth) {
        String targetYM = DateUtil.toString(targetYearMonth);
        String query = soqlSelectExchangeRate;
        query = query.replace(":StartYearMonth", appendSingleQuote(targetYM));
        query = query.replace(":EndYearMonth", appendSingleQuote(targetYM));
        QueryResult sExchangeRateResult = client.query(query);
        if (sExchangeRateResult.getSize() > 2) {
            throw new CommonException("ExchangeRate is wrong");
        }

        Map<String, ExchangeRateBean> map = new HashMap<>();
        List<SObject> sExchangeRateList = sExchangeRateResult.getRecords();
        for (SObject sExchangeRateObj : sExchangeRateList) {
            ExchangeRateC sExchangeRate = (ExchangeRateC) sExchangeRateObj;

            boolean isFixedRate = false;
            Boolean fixedRate = getValue(sExchangeRate.getFixedRateC());
            if (fixedRate != null) {
                isFixedRate = fixedRate.booleanValue();
            }

            ExchangeRateBean bean = new ExchangeRateBean();
            bean.setDollerYen(new BigDecimal(getValue(sExchangeRate.getDollerYenC())));
            bean.setFixedRate(isFixedRate);
            bean.setStartYearMonth(getValue(sExchangeRate.getStartYearMonthC()));
            bean.setEndYearMonth(getValue(sExchangeRate.getEndYearMonthC()));
            String key = null;
            if (isFixedRate) {
                // 固定レートの場合
                key = RATE_KIND_VALUE[1];
            } else {
                // 変動レートの場合
                key = RATE_KIND_VALUE[0];
            }
            map.put(key, bean);
        }
        return map;
    }

    /**
     * デフォルトのキャンペーングループリストを取得します。
     *
     * @param targetYearMonth 処理対象年月
     * @return デフォルトのキャンペーングループリスト
     */
    private static List<CampaignGroupBean> loadCampaignGroupDefaultList(YearMonth targetYearMonth) {
        String targetDate = getTargetDate(targetYearMonth);
        String query = soqlSelectCampaignGroupDefault;
        query = query.replace(":StartDate", targetDate);
        query = query.replace(":EndDate", targetDate);
        QueryResult sGroupResult = client.query(query);

        List<CampaignGroupBean> groupList = new ArrayList<>();
        List<SObject> sGroupList = sGroupResult.getRecords();
        for (SObject sGroupObj : sGroupList) {
            CampaignGroupC sGroup = (CampaignGroupC) sGroupObj;
            groupList.add(getCampaignGroupBean(sGroup));
        }
        return groupList;
    }

    /**
     * キャンペーングループリストを取得します。
     *
     * @param sCGRIResult キャンペーングループ関係情報クエリー結果
     * @param targetYearMonth 処理対象年月
     * @return キャンペーングループリスト
     */
    private static List<CampaignGroupBean> loadCampaignGroupList(QueryResult sCGRIResult, YearMonth targetYearMonth) {
        List<CampaignGroupBean> groupList = new ArrayList<>();

        if (sCGRIResult == null) {
            return groupList;
        }

        List<SObject> sCGRIList = sCGRIResult.getRecords();
        for (SObject sCGRIObj : sCGRIList) {
            CampaignGroupRelationInformationC sCGRI = (CampaignGroupRelationInformationC) sCGRIObj;
            String id = getValue(sCGRI.getCampaignGroupC());
            CampaignGroupBean groupBean = campaignGroupMap.get(id);
            if (groupBean == null) {
                String targetDate = getTargetDate(targetYearMonth);
                String query = soqlSelectCampaignGroup;
                query = query.replace(":StartDate", targetDate);
                query = query.replace(":EndDate", targetDate);
                query = query.replace(":Id", appendSingleQuote(id));
                QueryResult sGroupResult = client.query(query);
                List<SObject> sGropuList = sGroupResult.getRecords();
                if (sGropuList != null && !sGropuList.isEmpty()) {
                    CampaignGroupC sGroup = (CampaignGroupC) sGropuList.get(0);
                    groupBean = getCampaignGroupBean(sGroup);
                    campaignGroupMap.put(id, groupBean);
                    groupList.add(groupBean);
                }
            } else {
                groupList.add(groupBean);
            }
        }
        return groupList;
    }

    /**
     * キャンペーングループを返却します。
     *
     * @param sGroup Force.comから取得したキャンペーングループ
     * @return キャンペーングループ
     */
    private static CampaignGroupBean getCampaignGroupBean(CampaignGroupC sGroup) {
        CampaignGroupBean groupBean = new CampaignGroupBean();
        groupBean.setGroupId(getValue(sGroup.getGroupIdC()));
        groupBean.setCampaignName(getValue(sGroup.getCampaignNameC()));
        groupBean.setDescription(getValue(sGroup.getDescriptionC()));
        groupBean.setCampaignPlanList(loadCampaignPlanList(getValue(sGroup.getCampaignPlanCampaignGroupR())));
        return groupBean;
    }

    /**
     * キャンペーンプランリストを取得します。
     *
     * @param planResult キャンペーンプランクエリー結果
     * @return キャンペーンプランリスト
     */
    private static List<CampaignPlanBean> loadCampaignPlanList(QueryResult planResult) {
        List<CampaignPlanBean> planList = new ArrayList<>();

        if (planResult == null) {
            return planList;
        }

        List<SObject> sPlanList = planResult.getRecords();
        for (SObject sPlanObj : sPlanList) {
            CampaignPlanC sPlan = (CampaignPlanC) sPlanObj;
            CampaignPlanBean planBean = new CampaignPlanBean();
            planBean.setPlanId(getValue(sPlan.getPlanIdC()));
            planBean.setUsageType(getValue(sPlan.getUsageTypeC()));
            planBean.setProductName(getValue(sPlan.getProductNameC()));
            planBean.setCoefficient(new BigDecimal(getValue(sPlan.getCoefficientC())));
            planBean.setConstant(new BigDecimal(getValue(sPlan.getConstantC())));
            planBean.setDescription(getValue(sPlan.getDescriptionC()));

            // 適用開始日、適用終了日は、Salesforce上では日付型である。日付型は、年月日だけを保持しており、
            // 時分秒ミリ秒は保持していない。
            // よって、ZonedDateTime型に変換する際は、時分秒ミリ秒は0を設定している。
            XMLGregorianCalendar start = getValue(sPlan.getStartDateC());
            XMLGregorianCalendar end = getValue(sPlan.getEndDateC());
            planBean.setStartDate(DateUtil.toZonedDateTime(start.getYear(), start.getMonth(), start.getDay()));
            planBean.setEndDate(DateUtil.toZonedDateTime(end.getYear(), end.getMonth(), end.getDay()));

            planList.add(planBean);
        }
        return planList;
    }

    /**
     * 雑費グループを取得します。
     *
     * @param sGroup Force.comから取得した雑費グループ
     * @param targetYearMonth 処理対象年月
     * @return 雑費グループ
     */
    private static MiscellaneousExpensesGroupBean loadMiscellaneousExpensesGroup(MiscellaneousExpensesGroupC sGroup,
            YearMonth targetYearMonth) {
        if (sGroup == null) {
            return null;
        }

        String id = sGroup.getId();
        MiscellaneousExpensesGroupBean groupBean = miscellaneousExpensesGroupMap.get(id);
        if (groupBean == null) {
            groupBean = new MiscellaneousExpensesGroupBean();
            groupBean.setGroupId(getValue(sGroup.getGroupIdC()));
            groupBean.setMiscellaneousExpensesName(getValue(sGroup.getMiscellaneousExpensesNameC()));
            groupBean.setDescription(getValue(sGroup.getDescriptionC()));
            groupBean.setMiscellaneousExpensesPlanList(loadMiscellaneousExpensesPlanList(id, targetYearMonth));
            miscellaneousExpensesGroupMap.put(id, groupBean);
        }
        return groupBean;
    }

    /**
     * 雑費プランリストを取得します。
     *
     * @param miscellaneousExpensesGroup 雑費グループのID
     * @param targetYearMonth 処理対象年月
     * @return 雑費プランリスト
     */
    private static List<MiscellaneousExpensesPlanBean> loadMiscellaneousExpensesPlanList(
            String miscellaneousExpensesGroup, YearMonth targetYearMonth) {
        String targetDate = getTargetDate(targetYearMonth);
        String query = soqlSelectMiscellaneousExpensesPlan;
        query = query.replace(":MiscellaneousExpensesGroup", appendSingleQuote(miscellaneousExpensesGroup));
        query = query.replace(":StartDate", targetDate);
        query = query.replace(":EndDate", targetDate);
        QueryResult sPlanResult = client.query(query);

        List<MiscellaneousExpensesPlanBean> planList = new ArrayList<>();
        List<SObject> sPlanList = sPlanResult.getRecords();
        for (SObject sPlanObj : sPlanList) {
            MiscellaneousExpensesPlanC sPlan = (MiscellaneousExpensesPlanC) sPlanObj;
            MiscellaneousExpensesPlanBean planBean = new MiscellaneousExpensesPlanBean();
            planBean.setPlanId(getValue(sPlan.getPlanIdC()));
            planBean.setPlanName(getValue(sPlan.getName()));
            planBean.setCoefficient(new BigDecimal(getValue(sPlan.getCoefficientC())));
            planBean.setConstant(new BigDecimal(getValue(sPlan.getConstantC())));
            planBean.setDescription(getValue(sPlan.getDescriptionC()));

            // 適用開始日、適用終了日は、Salesforce上では日付型である。日付型は、年月日だけを保持しており、
            // 時分秒ミリ秒は保持していない。
            // よって、ZonedDateTime型に変換する際は、時分秒ミリ秒は0を設定している。
            XMLGregorianCalendar start = getValue(sPlan.getStartDateC());
            XMLGregorianCalendar end = getValue(sPlan.getEndDateC());
            planBean.setStartDate(DateUtil.toZonedDateTime(start.getYear(), start.getMonth(), start.getDay()));
            planBean.setEndDate(DateUtil.toZonedDateTime(end.getYear(), end.getMonth(), end.getDay()));

            planList.add(planBean);
        }

        return planList;
    }

    /**
     * クレジットリストを取得します。
     *
     * @param sCreditCouponResult クレジットクエリー結果
     * @param targetYearMonth 処理対象年月
     * @return クレジットリスト
     */
    private static List<CreditCouponBean> loadCreditCouponList(QueryResult sCreditCouponResult,
            YearMonth targetYearMonth) {
        List<CreditCouponBean> creditCouponList = new ArrayList<>();

        if (sCreditCouponResult == null) {
            return creditCouponList;
        }

        List<SObject> sCreditCouponList = sCreditCouponResult.getRecords();
        for (SObject sCreditCouponObj : sCreditCouponList) {
            CreditCouponC sCreditCoupon = (CreditCouponC) sCreditCouponObj;
            CreditCouponBean bean = new CreditCouponBean();
            bean.setId(sCreditCoupon.getId());
            bean.setCreditCouponId(getValue(sCreditCoupon.getCreditCouponIdC()));

            // 有効期限日は、Salesforce上では日付型である。日付型は、年月日だけを保持しており、
            // 時分秒ミリ秒は保持していない。
            // よって、ZonedDateTime型に変換する際は、時分秒ミリ秒は0を設定している。
            XMLGregorianCalendar xmlGCal = getValue(sCreditCoupon.getExpirationDateC());
            bean.setExpirationDate(DateUtil.toZonedDateTime(xmlGCal.getYear(), xmlGCal.getMonth(), xmlGCal.getDay()));

            Double ccb = getValue(sCreditCoupon.getCreditCouponBalanceC());
            if (ccb != null) {
                bean.setCreditCouponBalanceUSDollar(new BigDecimal(ccb));
            }
            bean.setCreditCouponBalanceUSDollarCalc(loadCreditCouponBalanceUSDollarCalc(bean.getId(), targetYearMonth));

            creditCouponList.add(bean);
        }
        return creditCouponList;
    }

    /**
     * 集計クレジット残高(米ドル)を取得します。<br>
     * 以下の内容で集計したクレジット残高(米ドル)を取得します。<br>
     * クレジット履歴年月が処理対象年月より小さい年月 or<br>
     * クレジット履歴年月が処理対象年月と同じ かつ クレジット履歴種別がクレジット登録履歴
     *
     * @param creditCoupon クレジットのID
     * @param targetYearMonth 処理対象年月
     * @return 集計クレジット残高(米ドル)
     */
    private static BigDecimal loadCreditCouponBalanceUSDollarCalc(String creditCoupon, YearMonth targetYearMonth) {
        BigDecimal creditCouponBalanceUSDollarCalc = null;

        String targetYM = DateUtil.toString(targetYearMonth);
        String query = soqlSelectCreditCouponHistory;
        query = query.replace(":CreditCoupon", appendSingleQuote(creditCoupon));
        query = query.replace(":HistoryYearMonth", appendSingleQuote(targetYM));
        QueryResult sHistoryResult = client.query(query);

        List<SObject> sHistoryList = sHistoryResult.getRecords();
        if (sHistoryList != null && !sHistoryList.isEmpty()) {
            AggregateResult aggregateResult = (AggregateResult) sHistoryList.get(0);
            List<Object> list = aggregateResult.getAny();
            if (list != null && !list.isEmpty()) {
                // CreditCouponHistoryValueタグを取得(CreditCouponHistoryValueタグの下に、値が入っている)。
                Element element = (Element) list.get(0);

                // タグ配下にある値を取得する
                NodeList nodeList = element.getChildNodes();
                if (nodeList != null && nodeList.getLength() > 0) {
                    Node node = nodeList.item(0);
                    if (node != null) {
                        if (creditCouponBalanceUSDollarCalc == null) {
                            creditCouponBalanceUSDollarCalc = new BigDecimal("0.00");
                        }
                        BigDecimal nodeValue = new BigDecimal(node.getNodeValue());
                        creditCouponBalanceUSDollarCalc = creditCouponBalanceUSDollarCalc.add(nodeValue);
                    }
                }
            }
        }
        return creditCouponBalanceUSDollarCalc;
    }

    /**
     * 税金グループを取得します。
     *
     * @param sGroup Force.comから取得した税金グループ
     * @param targetYearMonth 処理対象年月
     * @return 税金グループ
     */
    private static TaxGroupBean loadTaxGroup(TaxGroupC sGroup, YearMonth targetYearMonth) {

        // sGroup を未取得のSOQLでエラーとなるため、条件判定を追加
        if (sGroup == null) {
            return null;
        }

        String id = sGroup.getId();
        TaxGroupBean groupBean = taxGroupMap.get(id);
        if (groupBean == null) {
            groupBean = new TaxGroupBean();
            groupBean.setGroupId(getValue(sGroup.getGroupIdC()));
            groupBean.setTaxName(getValue(sGroup.getTaxNameC()));
            groupBean.setDescription(getValue(sGroup.getDescriptionC()));
            groupBean.setTaxPlanList(loadTaxPlanList(id, targetYearMonth));
            taxGroupMap.put(id, groupBean);
        }
        return groupBean;
    }

    /**
     * 税金プランリストを取得します。
     *
     * @param taxGroup 税金グループのID
     * @param targetYearMonth 処理対象年月
     * @return 税金プランリスト
     */
    private static List<TaxPlanBean> loadTaxPlanList(String taxGroup, YearMonth targetYearMonth) {
        String targetDate = getTargetDate(targetYearMonth);
        String query = soqlSelectTaxPlan;
        query = query.replace(":TaxGroup", appendSingleQuote(taxGroup));
        query = query.replace(":StartDate", targetDate);
        query = query.replace(":EndDate", targetDate);
        QueryResult sPlanResult = client.query(query);

        List<TaxPlanBean> planList = new ArrayList<>();
        List<SObject> sPlanList = sPlanResult.getRecords();
        for (SObject sPlanObj : sPlanList) {
            TaxPlanC sPlan = (TaxPlanC) sPlanObj;
            TaxPlanBean planBean = new TaxPlanBean();
            planBean.setPlanId(getValue(sPlan.getPlanIdC()));
            planBean.setTaxRate(new BigDecimal(getValue(sPlan.getTaxRateC())));
            planBean.setDescription(getValue(sPlan.getDescriptionC()));

            // 適用開始日、適用終了日は、Salesforce上では日付型である。日付型は、年月日だけを保持しており、
            // 時分秒ミリ秒は保持していない。
            // よって、ZonedDateTime型に変換する際は、時分秒ミリ秒は0を設定している。
            XMLGregorianCalendar start = getValue(sPlan.getStartDateC());
            XMLGregorianCalendar end = getValue(sPlan.getEndDateC());
            planBean.setStartDate(DateUtil.toZonedDateTime(start.getYear(), start.getMonth(), start.getDay()));
            planBean.setEndDate(DateUtil.toZonedDateTime(end.getYear(), end.getMonth(), end.getDay()));

            planList.add(planBean);
        }

        return planList;
    }

    /**
     * Force.comから処理対象のサブ契約情報を取得します。
     *
     * @param targetYearMonth 処理対象年月
     * @param query 実行するSOQL
     * @return 処理対象のサブ契約情報のリスト
     */
    private static List<SubContractInformationBean> loadSubContractInformation(String query) {
        ArrayList<SubContractInformationBean> subContractInfoList;

        login();

        subContractInfoList = new ArrayList<SubContractInformationBean>();
        try {
            QueryResult sContractInfoResult = client.query(query);

            String rateKind;

            // サブ契約情報リストを作成する。
            List<SObject> sContractInfoList = sContractInfoResult.getRecords();
            for (SObject sContractInfoObj : sContractInfoList) {
                SubContractInformationC sSubContractInfo = (SubContractInformationC) sContractInfoObj;
                ContractInformationC sContractInfo = getValue(sSubContractInfo.getContractInformationR());

                SubContractInformationBean subBean = new SubContractInformationBean();
                ContractInformationBean bean = new ContractInformationBean();
                subBean.setId(sSubContractInfo.getId());
                subBean.setSubContractNo(getValue(sSubContractInfo.getName()));
                subBean.setLinkedAccountId(getValue(sSubContractInfo.getLinkedAccountIdC()));
                subBean.setBillingPersonCity(getValue(sSubContractInfo.getBillingPersonCityC()));
                subBean.setBillingPersonStreet(getValue(sSubContractInfo.getBillingPersonStreetC()));
                subBean.setBillingPersonState(getValue(sSubContractInfo.getBillingPersonStateC()));
                subBean.setBillingPersonPostalCode(getValue(sSubContractInfo.getBillingPersonPostalCodeC()));
                subBean.setBillingPersonEmail(getValue(sSubContractInfo.getBillingPersonEmailC()));
                subBean.setBillingPersonCompany(getValue(sSubContractInfo.getBillingPersonCompanyC()));
                subBean.setBillingPersonDepartment(getValue(sSubContractInfo.getBillingPersonDepartmentC()));
                subBean.setBillingPersonTitle(getValue(sSubContractInfo.getBillingPersonTitleC()));
                Contact contact = getValue(sSubContractInfo.getBillingPersonR());
                if (contact != null) {
                    bean.setBillingPersonName(getValue(contact.getName()));
                }
                rateKind = getValue(sContractInfo.getRateKindC());
                bean.setFixedRate(rateKind);

                bean.setLinkedAccountId(getValue(sSubContractInfo.getLinkedAccountIdC()));

                bean.setStartDate(getDateValue(sContractInfo.getStartDateC()));
                bean.setCancelDate(getDateValue(sContractInfo.getCancelDateC()));

                bean.setNeedExcelBilling(getValue(sContractInfo.getNeedExcelBillingC()));
                bean.setSubResale2(getValue(sContractInfo.getSubResale2C()));
                bean.setContractNumber(getValue(sContractInfo.getName()));
                bean.setAccountNamePrefix(getValue(sContractInfo.getAccountNamePrefixC()));
                bean.setSupportPlan(getValue(sContractInfo.getSupportPlanC()));
                bean.setPricingGroupName(getValue(sContractInfo.getPricingGroupNameC()));

                ArrayList<OtherContactBean> otherList = new ArrayList<OtherContactBean>();

                // その他連絡先情報の取得
                QueryResult otherQR = getValue(sSubContractInfo.getOtherContactSubContractInformationR());
                if (otherQR != null) {
                    List<SObject> sOtherList = otherQR.getRecords();
                    for (SObject sOtherObj : sOtherList) {
                        OtherContactC sOther = (OtherContactC) sOtherObj;
                        OtherContactBean otherBean = new OtherContactBean();
                        otherBean.setLinkedAccountId(getValue(sOther.getLinkedAccountIdC()));
                        otherBean.setEmail(getValue(sOther.getEmailC()));
                        otherBean.setKind(getValue(sOther.getKindC()));
                        otherBean.setCompany(getValue(sOther.getCompanyC()));

                        Contact person = getValue(sOther.getPersonR());
                        if (person != null) {
                            ContactBean personBean = new ContactBean();
                            personBean.setId(person.getId());
                            personBean.setName(getValue(person.getName()));
                            personBean.setAccountId(getValue(person.getAccountId()));
                            personBean.setFirstName(getValue(person.getFirstName()));
                            personBean.setLastName(getValue(person.getLastName()));
                            personBean.setDepartment(getValue(person.getDepartment()));
                            personBean.setEmail(getValue(person.getEmail()));
                            otherBean.setPersonBean(personBean);
                        }

                        otherList.add(otherBean);
                    }
                }
                subBean.setOtherContact(otherList);

                // iamユーザ情報の取得
                IamUserBean iamUser = new IamUserBean();
                iamUser.setAccessKey(getValue(sContractInfo.getApiIamAccessKeyC()));
                iamUser.setSecurityKey(getValue(sContractInfo.getApiIamSecurityKeyC()));
                bean.setApiIamUser(iamUser);

                subBean.setContractInformation(bean);
                subContractInfoList.add(subBean);
            }
        } finally {
            logout();
        }
        return subContractInfoList;
    }

    /**
     * 処理対象年月から検索条件に設定する文字列(uuuu-MM-dd形式)を返却します。<br>
     * 日は処理対象年月の1日, 時分秒ミリ秒は0として文字列を作成します。
     *
     * @param targetYearMonth 処理対象年月
     * @return 検索条件に設定する文字列
     */
    private static String getTargetDate(YearMonth targetYearMonth) {
        int year = targetYearMonth.getYear();
        int month = targetYearMonth.getMonthValue();
        int day = 1;
        return DateUtil.toString(DateUtil.toZonedDateTime(year, month, day, 0, 0, 0), TARGET_DATE_FM);
    }

    /**
     * 引数のJAXBElementの値を返却します。
     *
     * @param element JAXBElement
     * @return 値
     */
    private static <T> T getValue(JAXBElement<T> element) {
        T result = null;
        if (element != null) {
            result = element.getValue();
        }
        return result;
    }

    /**
     * 引数のJAXBElementの値を返却します。(boolean用)
     *
     * @param element JAXBElement<Boolean>
     * @return 値
     */
    private static boolean getBoolValue(JAXBElement<Boolean> element, boolean defValue) {
        boolean result = defValue;

        if (element != null) {
            Boolean boolItem = getValue(element);
            if (boolItem != null) {
                result = boolItem.booleanValue();
            }
        }
        return result;
    }

    /**
     * 引数のJAXBElementの値を返却します。(日付用)
     *
     * @param element JAXBElement<XMLGregorianCalendar>
     * @return 値
     */
    private static ZonedDateTime getDateValue(JAXBElement<XMLGregorianCalendar> element) {
        ZonedDateTime result = null;

        if (element != null) {
            XMLGregorianCalendar calendar = getValue(element);
            if (calendar != null) {
                result = DateUtil.toZonedDateTime(calendar.getYear(), calendar.getMonth(), calendar.getDay());
            }
        }
        return result;
    }

    /**
     * JAXBElementに変換して返却します。
     *
     * @param apiName API名
     * @param value 値
     * @return JAXBElementに変換した値
     */
    @SuppressWarnings("unchecked")
    private static <T> JAXBElement<T> getJAXBElement(String apiName, T value) {
        return new JAXBElement<T>(new QName(apiName), (Class<T>) value.getClass(), value);
    }

    /**
     * 文字列の両端にシングルクオートを追加して返却します。
     *
     * @param value 文字列
     * @return 両端にシングルクオートを追加した文字列
     */
    private static String appendSingleQuote(String value) {
        return arroundSingleQuote + value + arroundSingleQuote;
    }

    /**
     * 文字列のリストを、セパレータ区切りの1行の文字列に変換します。<br>
     * 文字列は、引数のarroundで指定した文字で括ります。
     *
     * @param valueList 文字列のリスト
     * @return 変換後の文字列
     */
    private static String join(List<String> valueList) {
        StringBuilder builder = new StringBuilder();
        if (valueList != null) {
            for (int i = 0; i < valueList.size(); i++) {
                if (i > 0) {
                    builder.append(separatorCamma);
                }
                builder.append(appendSingleQuote(valueList.get(i)));
            }
        }
        return builder.toString();
    }

    /**
     * カスタムオブジェクトのリストをUpsertします。
     *
     * @param sObjectList カスタムオブジェクトのリスト
     */
    private static void upsert(List<SObject> sObjectList) {
        upsert(sObjectList, "ExId__c");
    }

    /**
     * カスタムオブジェクトのリストをUpsertします。
     *
     * @param sObjectList カスタムオブジェクトのリスト
     * @param externalIDFieldName 対象データ抽出条件となる外部項目名
     * @return upsert実施結果
     */
    private static List<UpsertResult> upsert(List<SObject> sObjectList, String externalIDFieldName) {
        if (!sObjectList.isEmpty()) {
            int count = 0;
            int max = AppInfoUtil.getIntVal("Sforce", "SforceMaxUpsertCount", 200);
            List<SObject> list = new ArrayList<>();
            List<UpsertResult> resultList = new ArrayList<>();
            for (SObject sObject : sObjectList) {
                count++;
                list.add(sObject);
                if (count == max) {
                    // 1回でupsertする最大件数になった場合
                    List<UpsertResult> upsertResultList = client.upsert(externalIDFieldName, list);
                    resultList.addAll(upsertResultList);
                    checkResultList(upsertResultList, list.size());

                    count = 0;
                    list = new ArrayList<>();
                }
            }
            if (count != 0) {
                // upsertしていないものがある場合
                List<UpsertResult> upsertResultList = client.upsert(externalIDFieldName, list);
                resultList.addAll(upsertResultList);
                checkResultList(upsertResultList, list.size());
            }
            return resultList;
        }
        return null;
    }

    /**
     * 削除IDリストに含まれるオブジェクトIDに対応するカスタムオブジェクトを削除します。
     *
     * @param delIdList 削除IDリスト
     * @return delete実施結果
     */
    private static List<DeleteResult> delete(List<String> delIdList) {
        if (!delIdList.isEmpty()) {
            int count = 0;
            int max = AppInfoUtil.getIntVal("Sforce", "MaxDeleteCount", 200);
            List<String> list = new ArrayList<>();
            List<DeleteResult> resultList = new ArrayList<>();
            for (String delId : delIdList) {
                count++;
                list.add(delId);
                if (count == max) {
                    // 1回でdeleteする最大件数になった場合
                    List<DeleteResult> deleteResultList = client.delete(list);
                    resultList.addAll(deleteResultList);
                    checkResultList(deleteResultList, list.size());

                    count = 0;
                    list = new ArrayList<>();
                }
            }
            if (count != 0) {
                // deleteしていないものがある場合
                List<DeleteResult> deleteResultList = client.delete(list);
                resultList.addAll(deleteResultList);
                checkResultList(deleteResultList, list.size());
            }
            return resultList;
        }
        return null;
    }

    /**
     * Upsert or 削除の結果をチェックします。
     *
     * @param resultList Upsert or 削除の結果に関するリスト
     * @param inputListSize Upsert or 削除を実施した数
     */
    private static <T> void checkResultList(List<T> resultList, int inputListSize) {
        if (resultList.size() != inputListSize) {
            throw new CommonException("resultList size wrong");
        }

        boolean isCheckNG = false;
        for (T result : resultList) {
            boolean isSuccess = false;
            List<Error> errorList = null;
            if (result instanceof UpsertResult) {
                UpsertResult upsertResult = (UpsertResult) result;
                isSuccess = upsertResult.isSuccess();
                errorList = upsertResult.getErrors();
            } else if (result instanceof DeleteResult) {
                DeleteResult deleteResult = (DeleteResult) result;
                isSuccess = deleteResult.isSuccess();
                errorList = deleteResult.getErrors();
            } else {
                throw new CommonException("result type wrong");
            }

            if (isSuccess) {
                // 成功している場合
                continue;
            }

            if (!isCheckNG) {
                isCheckNG = true;
            }

            // エラーログを出力する。
            if (errorList == null || errorList.size() <= 0) {
                logger.error("errorList is null or empty");
            } else {
                for (Error error : errorList) {
                    StringBuilder builder = new StringBuilder();
                    builder.append("StatusCode=");
                    builder.append(error.getStatusCode());
                    builder.append(",Message=");
                    builder.append(error.getMessage());
                    List<String> fieldList = error.getFields();
                    for (String field : fieldList) {
                        builder.append(",field=");
                        builder.append(field);
                    }
                    logger.error(builder.toString());
                }
            }
        }

        if (isCheckNG) {
            throw new CommonException("upsert fail");
        }
    }

    /**
     * ログインします。
     */
    private static void login() {
        boolean isSforceConnect = Boolean.valueOf(PropertiesUtil.getProperty("SforceConnect", "true"));
        if (isSforceConnect) {
            String category = "Sforce";
            String loginId = AppInfoUtil.getVal(category, "LoginId");
            String encryptLoginPassword = AppInfoUtil.getVal(category, "EncryptLoginPassword");

            String loginPass = null;
            if (encryptLoginPassword == null) {
                loginPass = AppInfoUtil.getVal(category, "LoginPassword");
            } else {
                AwsKmsHelper helper = new AwsKmsHelper();
                loginPass = helper.decrypt(encryptLoginPassword);
            }

            String proxyHost = PropertiesUtil.getProperty("ProxyHost");
            String proxyPort = PropertiesUtil.getProperty("ProxyPort");
            String nonProxyHosts = PropertiesUtil.getProperty("NonProxyHosts");
            if (logger.isDebugEnabled()) {
                // loginPassは出力しない
                logger.debug("Sforce,LoginId=" + loginId);
                logger.debug("ProxyHost=" + proxyHost);
                logger.debug("ProxyPort=" + proxyPort);
                logger.debug("NonProxyHosts=" + nonProxyHosts);
            }

            if (StringUtils.isEmpty(loginId)) {
                throw new CommonException("sforce login id is null or empty");
            }
            if (StringUtils.isEmpty(loginPass)) {
                throw new CommonException("sforce login pass is null or empty");
            }
            String pHost = null;
            String pPort = null;
            String pNhosts = null;
            if (StringUtils.isEmpty(proxyHost)) {
                if (!StringUtils.isEmpty(proxyPort) || !StringUtils.isEmpty(nonProxyHosts)) {
                    throw new CommonException(
                            "proxy host is null or empty, proxy port and non proxy hosts is not null and empty");
                }
            } else {
                if (StringUtils.isEmpty(proxyPort)) {
                    throw new CommonException("proxy host is not null and empty, proxy port is null or empty");
                } else {
                    pHost = proxyHost;
                    pPort = proxyPort;
                    pNhosts = nonProxyHosts;
                }
            }

            client = new SforceClient();
            if (StringUtils.isEmpty(pHost)) {
                client.login(loginId, loginPass);
            } else {
                client.login(loginId, loginPass, pHost, pPort, pNhosts);
            }
        }
    }

    /**
     * ログアウトします。
     */
    private static void logout() {
        boolean isSforceConnect = Boolean.valueOf(PropertiesUtil.getProperty("SforceConnect", "true"));
        if (isSforceConnect) {
            if (client != null) {
                client.logout();
            }
        }
    }
}
